//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using OpenLS.Core;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Extensibility;

namespace OpenLS.Spreadsheet
{
    public class SingleCellRange : CellsRange
    {
        internal SingleCellRange(Cell cell)
            : this(cell.Worksheet, cell.Address)
        {
            Contract.Requires(cell != null);
        }
        internal SingleCellRange(Worksheet worksheet, CellAddress address)
        {
            Contract.Requires(worksheet != null);
            _worksheet = worksheet;
            cellAddress = address;
            _worksheet.AddWeakRangeChanged(OnRangeChanged);
        }

        private void OnRangeChanged(object sender, RangeChangeEventArgs e)
        {
            if (sender == this)
                return;
            if (this.Worksheet.Workbook.UndoManager.State == UndoState.Undo)
                return;

            switch (e.Type)
            {
                case RangeChangeType.Deletion:
                    {
                        switch (e.Hint)
                        {
                            case CellMoveOrientationHint.Vertical:
                                {
                                    if (e.Address.TopLeft.Column > Address.TopLeft.Column || e.Address.TopRight.Column < Address.TopRight.Column)
                                        return;
                                    int otherStart = e.Address.TopLeft.Row;
                                    int otherEnd = e.Address.BottomRight.Row;
                                    int myStart = Address.TopLeft.Row;
                                    if (otherEnd < myStart)
                                        SetAddress(this.cellAddress + new CellRelativeAddress(otherStart - otherEnd - 1, 0));
                                    else if (otherStart > myStart)
                                    {
                                        // Nothing to do
                                    }
                                    else
                                    {
                                        // make invalid
                                    }
                                }
                                break;
                            case CellMoveOrientationHint.Horizontal:
                                {
                                    if (e.Address.TopLeft.Row > Address.TopLeft.Row || e.Address.BottomRight.Row < Address.BottomRight.Row)
                                        return;

                                    int otherStart = e.Address.TopLeft.Column;
                                    int otherEnd = e.Address.BottomRight.Column;
                                    int myStart = Address.TopLeft.Column;
                                    if (otherEnd < myStart)
                                        SetAddress(cellAddress + new CellRelativeAddress(0, otherStart - otherEnd - 1));
                                    else if (otherStart > myStart)
                                    {
                                        // Nothing to do
                                    }
                                    else
                                    {
                                        // make invalid
                                    }
                                }
                                break;
                        }
                    }
                    break;
                case RangeChangeType.Insertion:
                    switch (e.Hint)
                    {
                        case CellMoveOrientationHint.Vertical:
                            {
                                if (e.Address.TopLeft.Column > Address.TopLeft.Column || e.Address.BottomRight.Column < Address.BottomRight.Column)
                                    return;
                                if (e.Address.TopLeft.Row <= this.cellAddress.Row)
                                    SetAddress(cellAddress + new CellRelativeAddress(e.Address.RowSpan, 0));

                                
                            }
                            break;

                        case CellMoveOrientationHint.Horizontal:
                            {
                                if (e.Address.TopLeft.Row > Address.TopLeft.Row || e.Address.BottomRight.Row < Address.BottomRight.Row)
                                    return;

                                if (e.Address.TopLeft.Column <= this.cellAddress.Column)
                                    SetAddress(cellAddress + new CellRelativeAddress(0, e.Address.ColumnSpan));
                            }
                            break;
                        default:
                            throw new NotSupportedException();
                    }
                    break;
            }

        }

        private void SetAddress(CellAddress address)
        {
            if (cellAddress == address)
                return;
            this.Worksheet.Workbook.AddUndoAction(cellAddress, SetAddress);
            cellAddress = address;
        }

        internal override SimpleRange ProjectedSimple(Worksheet worksheet, CellRelativeAddress diff)
        {
            return new SingleCellRange(worksheet, cellAddress + diff);
        }

        internal override RangeReference ToReference()
        {
            return new SingleCellRangeReference(new DynamicCellReference(new AnchoredRowReference(TopLeftCell.Row, true),
                new AnchoredColumnReference(TopLeftCell.Column, true)));
        }
        public override int Column
        {
            get
            {
                return Address.TopLeft.Column;
            }
        }
        public override int Row
        {
            get
            {
                return Address.TopLeft.Row;
            }
        }
        internal override IEnumerable<PartialRowOrColumnRange> GetPartialRowOrColumnRanges(RowOrColumn rc)
        {
            yield return new PartialRowOrColumnRange(Worksheet, cellAddress, 1, rc);
        }

        internal override void SetCellValue(CellProperty property, object value)
        {
            object o = Cell.GetCellValue(property);
            if (Equals(o, value))
                return;
            base.SetCellValue(property, value);
        }
        internal override CellFormattingRecord GetFormattingRecord()
        {
            return Cell.FormattingRecord;
        }
        internal override IEnumerable<CellFormattingRecord> GetFormattingRecords()
        {
            yield return this.Cell.FormattingRecord;
        }
        internal override string ShortLabel
        {
            get
            {
                return Cell.Label;
            }
        }
        internal override void SetCellValueNoUndo(CellProperty p, object value)
        {
            Cell.SetCellValue(p, value);
        }

        readonly Worksheet _worksheet;
        CellAddress cellAddress;
        internal override Cell FirstCell
        {
            get { return _worksheet[cellAddress]; }
        }

        public override int RowSpan
        {
            get { return Cell.RowSpan; }
        }

        public override int ColumnSpan
        {
            get { return Cell.ColumnSpan; }
        }

        public override Worksheet Worksheet
        {
            get { return _worksheet; }
        }

        public override string Label
        {
            get { return Address.Label ; }
        }

        internal override object GetFirstCellValue(CellProperty p)
        {
            return Cell.GetCellValue(p);
        }

        internal override object GetCellValue(CellProperty p, out bool uniform)
        {
            uniform = true;
            return Cell.GetCellValue(p);
        }

        internal override Border GetBorder(BorderType borderType, out bool uniform)
        {
            uniform = true;
            var borders = Cell.ActualBorders;
            if (borders == null)
                return null;
            switch (borderType)
            {
                case BorderType.LeftEdge:
                    return borders.Left;
                    case BorderType.RightEdge:
                    return borders.Right;
            case BorderType.TopEdge:
                    return borders.Top;
            case BorderType.BottomEdge:
                    return borders.Bottom;
            case BorderType.DiagonalDown:
                    return borders.DiagonalDown;
            case BorderType.DiagonalUp:
                    return borders.DiagonalUp;
                    case BorderType.InsideHorizontal:
                    case BorderType.InsideVertical:
                    return null;
                default:
                    throw new NotSupportedException();
            }
        }

        internal override IEnumerable<Cell> GetNonEmptyCells()
        {
            if (!Cell.IsVolatile)
                yield return Cell;  
        }


        internal override bool Contains(CellAddress address)
        {
            return cellAddress.Equals( address);
        }

        internal override void SetStyleNoUndo(CellStyle s)
        {
            Cell.Style = (s);
        }

        public override IEnumerable<Cell> GetAllCells()
        {
            yield return Cell;
        }

        internal override RangeAddress Address
        {
            get { return new RangeAddress(cellAddress, cellAddress); }
        }
        internal Cell Cell
        {
            get { return _worksheet[cellAddress] ; }
        }

        public override Cell TopLeftCell
        {
            get { return Cell; }
        }

        public override Cell BottomLeft
        {
            get { return Cell; }
        }

        public override Cell TopRight
        {
            get { return Cell; }
        }

        public override Cell BottomRight
        {
            get { return Cell; }
        }

        public override IEnumerable<Row> GetRows()
        {
            yield return _worksheet.Rows[Row];
        }

        public override IEnumerable<Column> GetColumns()
        {
            yield return _worksheet.Columns[Column];
        }

        internal override IEnumerable<Cell> GetLeftCells()
        {
            yield return Cell;
        }

        internal override IEnumerable<Cell> GetRightCells()
        {
            yield return Cell;
        }

        internal override IEnumerable<Cell> GetTopCells()
        {
            yield return Cell;
        }

        internal override IEnumerable<Cell> GetBottomCells()
        {
            yield return Cell;
        }

        public override Cell this[int row, int column]
        {
            get
            {
                if (row == 0 && column == 0)
                    return Cell;
                return Cell.Worksheet[Row + row, Column + column];
            }
        }

        internal override Cell GetNextCell(Cell cell, CellNavigationDirection direction)
        {
            return Worksheet[cellAddress];
        }

        internal override Range GetColumnRange(int index, RangeType type)
        {
            if (index == 0)
                return this;
            return new SingleCellRange(Worksheet, Cell.Address + new CellRelativeAddress(0, index));
        }

        internal override void SetBorderNoUndo(Border border, BorderType borderType)
        {
            Cell.SetBorder(border, borderType);
                    }

       

        internal override Range GetRowRange(int index, RangeType type)
        {
            if (index == 0)
                return this;
            return new SingleCellRange(Worksheet, Cell.Address + new CellRelativeAddress(index, 0));
        }

        internal override Range Offseted(CellRelativeAddress address)
        {
            return new SingleCellRange(Cell + address);
        }
    }
}
